#ifndef INC_TWEAKERTOOL_H
#define INC_TWEAKERTOOL_H

#include "Tool.h"
#include "../model/UMLLine.h" // for UMLLine::size_type
#include "../model/DiagramVisitor.h"

#include <memory>

class UMLObject;
class MoveCmd;
class ResizeCmd;
class MoveVertexCmd;

class TweakerTool : public Tool
{
	public:
		TweakerTool(DiagramMan&);

	protected:

		/*** MOUSE HOOKS ***/
		bool press(QMouseEvent&);
		void click(QMouseEvent&);
		void move(QMouseEvent&);
		void startDrag(QPoint&);
		void drag(QMouseEvent&);
		void endDrag(QMouseEvent&);

	private:

		void updateState(const QPoint& newpos);

		// ABC for State pattern used for dragging
		class State
		{
			public:
				State(TweakerTool* context);

				/*** EVENT HANDLERS ***/

				void startDrag(QPoint&);
				virtual void drag(QMouseEvent&);
				virtual void endDrag(QMouseEvent&);

				/**
				 * All states should define this to actually set TT's _state,
				 * set the proper mouse cursor, and perform any other necessary
				 * initializations. (But it should be quick!)
				 */
				virtual void takeControl() = 0;

			protected:

				/*** ACCESSORS ***/

				TweakerTool& context();
				const TweakerTool& context() const;

				/**
				 * Returns the starting position of the mouse in screen
				 * coordinates.
				 */
				QPoint initialMouse() const;

				/*** SUBCLASS HOOKS ***/

				/**
				 * Called when the user begins a drag operation. initialMouse()
				 * will already be initialized. Allows subclass to perform any
				 * initialization (resource allocation, etc.) for dragging.
				 *
				 * Default implemenation does nothing.
				 */
				virtual void init();

				/* Note: we provide current pos and not deltas because deltas
				 * are less useful for line manipulation and it's not a hard
				 * mathematical operation to subtract. :) */

				/**
				 * Called when the user moves the mouse during a drag operation
				 * so that the State can update the UI.
				 *
				 * Current mouse position (in screen coords) is provided,
				 * subtract initialMouse() to get deltas.
				 */
				virtual void drag(const QPoint& current) = 0;
				virtual void endDrag(const QPoint& current) = 0;

			private:
				TweakerTool* _context; // init-checked
				QPoint _initialMouse;
		};

		/**
		 * Like State, but provides subclasses logical coordinates instead of
		 * screen coordinates via initialMouse(), drag(), and endDrag().
		 *
		 * Note: Yes, drag() is only called when /logical/ coords change.
		 */
		class LCState : public State
		{
			public:
				LCState(TweakerTool* context);

				void drag(QMouseEvent&);
				void endDrag(QMouseEvent&);

			protected:

				/**
				 * We don't need State::initialMouse() to be virtual because
				 * it's protected: only subclasses will call it.
				 *
				 * @return logical initial mouse coordinates.
				 */
				QPoint initialMouse() const;

				void drag(const QPoint& current) = 0;
				void endDrag(const QPoint& current) = 0;

			private:

				/**
				 * Stores previous logical coordinates so we can decide whether
				 * to call drag() or not.
				 */
				QPoint _previous;
		};

		class BlankState : public State
		{
			public:
				BlankState(TweakerTool* context);

				void takeControl();

				void drag(const QPoint&);
				void endDrag(const QPoint&);

			private:
				QRect _ants;

		} _blankState;

		/**
		 * Moves a box! Can actually be used for lines, too.
		 */
		class BoxMoveState : public LCState
		{
			public:
				BoxMoveState(TweakerTool* context);

				/*** ACCESSORS ***/

				/**
				 * Sets the target to move. There are reasons why we don't
				 * provide a getter.
				 */
				void setTarget(UMLObject*);

				/*** STATE REQUIREMENTS ***/

				void takeControl();

				void init();
				void drag(const QPoint&);
				void endDrag(const QPoint&);

			private:

				/**
				 * @return true if the position was updated
				 */
				void updateCmd(const QPoint& mouse);

				/**
				 * Stores initial coordinates of _target.
				 */
				QPoint _initial;

				UMLObject* _target;

				std::auto_ptr<MoveCmd> _cmd;

		} _boxMoveState;

		class BoxMoveBState : public LCState
		{
			public:
				BoxMoveBState(TweakerTool* context);

				/*** ACCESSORS ***/

				void setTarget(UMLBox& box);

				/*** STATE REQUIREMENTS ***/

				void takeControl();

				void init();
				void drag(const QPoint&);
				void endDrag(const QPoint&);

			private:
				void updateCmd(const QPoint&);

				UMLBox* _target;
				QSize _original;
				std::auto_ptr<ResizeCmd> _cmd;

		} _boxMoveBState;

		class BoxMoveRState : public LCState
		{
			public:
				BoxMoveRState(TweakerTool* context);

				/*** ACCESSORS ***/

				void setTarget(UMLBox& box);

				/*** STATE REQUIREMENTS ***/

				void takeControl();

				void init();
				void drag(const QPoint&);
				void endDrag(const QPoint&);

			private:
				void updateCmd(const QPoint&);

				UMLBox* _target;
				QSize _original;
				std::auto_ptr<ResizeCmd> _cmd;

		} _boxMoveRState;

		class VertexMoveState : public LCState
		{
			public:
				VertexMoveState(TweakerTool* context);

				/*** ACCESSORS ***/

				UMLLine* target() const;
				void setTarget(UMLLine*);

				UMLLine::size_type vertex() const;
				void setVertex(UMLLine::size_type);

				/*** STATE REQUIREMENTS ***/

				void takeControl();

			protected:
				void init();
				void drag(const QPoint&);
				void endDrag(const QPoint&);

				Command& command();

				/**
				 * Releases ownership of the internal Command to the caller.
				 */
				std::auto_ptr<Command> commandPtr();

			private:

				UMLLine* _target;
				UMLLine::size_type _vertex;

				QPoint _original;

				std::auto_ptr<MoveVertexCmd> _cmd;

				void updateCmd(const QPoint&);

		} _vertexMoveState;

		class StateSwitchVisitor : public DiagramVisitor
		{
			public:
				StateSwitchVisitor(TweakerTool& daddy, const QPoint& pt);

				/**
				 * Visit the specified box, determine which state is
				 * appropriate, initialize that state for action, but leaves it
				 * up to TweakerTool to switch to that state.
				 */
				void visit(UMLBox&);

				/**
				 * See visit(Box).
				 */
				void visit(UMLLine&);

				State* state() const;

			private:
				TweakerTool& _daddy;
				QPoint _pt;
				State* _state;  // init-checked
		};

		// should never be NULL
		State* _state; // init-checked
		UMLObject* _lastSel;

		// screen coords
		QPoint _lastClick;
};

inline TweakerTool& TweakerTool::State::context()
{
	return *_context;
}

inline const TweakerTool& TweakerTool::State::context() const
{
	return *_context;
}

inline QPoint TweakerTool::State::initialMouse() const
{
	return _initialMouse;
}

inline QPoint TweakerTool::LCState::initialMouse() const
{
	return context().tological(State::initialMouse());
}

#endif // INC_TWEAKERTOOL_H

